{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-02-23T01:55:39.852842Z",
     "start_time": "2025-02-23T01:55:39.846919Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.1\n",
      "torch 2.6.0+cu118\n",
      "cuda:0\n"
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 准备数据",
   "id": "67576f081bec3335"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:00:25.745186Z",
     "start_time": "2025-02-23T02:00:25.709860Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home='../data')\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "id": "aba266b5d46207c1",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 7
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:00:38.055301Z",
     "start_time": "2025-02-23T02:00:38.051176Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:5])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:5])"
   ],
   "id": "ef5a190394b5d956",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02],\n",
      "       [ 7.25740000e+00,  5.20000000e+01,  8.28813559e+00,\n",
      "         1.07344633e+00,  4.96000000e+02,  2.80225989e+00,\n",
      "         3.78500000e+01, -1.22240000e+02],\n",
      "       [ 5.64310000e+00,  5.20000000e+01,  5.81735160e+00,\n",
      "         1.07305936e+00,  5.58000000e+02,  2.54794521e+00,\n",
      "         3.78500000e+01, -1.22250000e+02],\n",
      "       [ 3.84620000e+00,  5.20000000e+01,  6.28185328e+00,\n",
      "         1.08108108e+00,  5.65000000e+02,  2.18146718e+00,\n",
      "         3.78500000e+01, -1.22250000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585, 3.521, 3.413, 3.422])\n"
     ]
    }
   ],
   "execution_count": 8
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:00:40.715490Z",
     "start_time": "2025-02-23T02:00:40.636215Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "}\n"
   ],
   "id": "3ddb26ec5fe4465",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 9
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:02:26.479895Z",
     "start_time": "2025-02-23T02:02:26.472849Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(x_train)\n",
    "dataset_maps[\"train\"][0:1]"
   ],
   "id": "28069ee51bfcd3c5",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[array([[   5.345     ,   32.        ,    5.13541667, ...,    2.88194444,\n",
       "           34.38      , -119.74      ],\n",
       "        [   3.2813    ,   33.        ,    5.15268817, ...,    2.99139785,\n",
       "           37.96      , -121.7       ],\n",
       "        [   3.7833    ,   17.        ,    6.05769231, ...,    2.81089744,\n",
       "           39.07      , -123.21      ],\n",
       "        ...,\n",
       "        [   1.7574    ,   11.        ,    4.        , ...,    1.98447894,\n",
       "           39.42      , -123.85      ],\n",
       "        [   4.45      ,   32.        ,    5.15618661, ...,    2.59229209,\n",
       "           34.24      , -118.28      ],\n",
       "        [   3.6346    ,   22.        ,    6.0521327 , ...,    2.71800948,\n",
       "           33.19      , -117.23      ]])]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 13
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:08:08.636319Z",
     "start_time": "2025-02-23T02:08:08.629241Z"
    }
   },
   "cell_type": "code",
   "source": [
    "x, y = dataset_maps[\"train\"]\n",
    "torch.from_numpy(scaler.transform(x)).float()"
   ],
   "id": "3d9810e3199fd73a",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[ 0.8015,  0.2722, -0.1162,  ..., -0.0210, -0.5898, -0.0824],\n",
       "        [-0.2981,  0.3523, -0.1092,  ..., -0.0060,  1.0806, -1.0611],\n",
       "        [-0.0306, -0.9293,  0.2596,  ..., -0.0308,  1.5984, -1.8152],\n",
       "        ...,\n",
       "        [-1.1101, -1.4099, -0.5790,  ..., -0.1441,  1.7617, -2.1347],\n",
       "        [ 0.3247,  0.2722, -0.1078,  ..., -0.0607, -0.6551,  0.6466],\n",
       "        [-0.1098, -0.5288,  0.2574,  ..., -0.0435, -1.1450,  1.1709]])"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 24
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:06:50.494341Z",
     "start_time": "2025-02-23T02:06:50.488536Z"
    }
   },
   "cell_type": "code",
   "source": "torch.from_numpy(y).float().reshape(-1, 1)[0:10]",
   "id": "303ed9f5fbbd5661",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[3.2260],\n",
       "        [1.5140],\n",
       "        [1.5980],\n",
       "        [5.0000],\n",
       "        [5.0000],\n",
       "        [1.4310],\n",
       "        [2.5190],\n",
       "        [5.0000],\n",
       "        [1.3540],\n",
       "        [4.7210]])"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 22
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "### 构建数据集\n",
    "\n",
    "这里我们构建多输入的数据集，注意到数据集介绍里对每个特征定义如下：\n",
    "\n",
    "> **Attribute Information**:\n",
    "> - MedInc        median income in block group\n",
    "> - HouseAge      median house age in block group\n",
    "> - AveRooms      average number of rooms per household\n",
    "> - AveBedrms     average number of bedrooms per household\n",
    "> - Population    block group population\n",
    "> - AveOccup      average number of household members\n",
    "> - Latitude      block group latitude\n",
    "> - Longitude     block group longitude\n",
    "\n",
    "我们认为最后两维作为位置信息要单独处理，故制作数据集如下"
   ],
   "id": "29f6d2cd4a534e20"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:09:00.258412Z",
     "start_time": "2025-02-23T02:09:00.250601Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "            \n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "    \n",
    "    def __getitem__(self, idx):\n",
    "        return (self.x[idx],self.x[idx][-2:]), self.y[idx] #返回的是一个元组，元组里面是两个元素，第一个元素是一个元组，第二个元素是一个数。self.x[idx][-2:]代表取最后两个元素\n",
    "    \n",
    "    \n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "id": "fa7eea3fc2e5a6c1",
   "outputs": [],
   "execution_count": 25
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:09:11.255796Z",
     "start_time": "2025-02-23T02:09:11.251981Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "batch_size = 8\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=False)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "id": "e8a52e9c269bbb47",
   "outputs": [],
   "execution_count": 26
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:09:18.934567Z",
     "start_time": "2025-02-23T02:09:18.928572Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class WideDeep(nn.Module):\n",
    "    def __init__(self, input_dim=[8, 2]):\n",
    "        super().__init__()\n",
    "        self.deep = nn.Sequential(\n",
    "            nn.Linear(input_dim[1], 30),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 30),\n",
    "            nn.ReLU()\n",
    "            )\n",
    "        # pytorch 需要自行计算输出输出维度\n",
    "        self.output_layer = nn.Linear(30 + input_dim[0], 1)\n",
    "        \n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "        \n",
    "    def forward(self, x_wide, x_deep):\n",
    "        # x_deep.shape [batch size, 6]\n",
    "        deep_output = self.deep(x_deep)\n",
    "        # concat [batch size, 30] with [batch size 8]\n",
    "        concat = torch.cat([x_wide, deep_output], dim=1)\n",
    "        logits = self.output_layer(concat)\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "id": "4b3cb1e0c8286653",
   "outputs": [],
   "execution_count": 27
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:44:36.266113Z",
     "start_time": "2025-02-23T02:44:36.260982Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "id": "20f57f73ffb293a9",
   "outputs": [],
   "execution_count": 28
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:45:31.317101Z",
     "start_time": "2025-02-23T02:45:31.304104Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model = WideDeep()\n",
    "model"
   ],
   "id": "1a1ac825861bd42a",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "WideDeep(\n",
       "  (deep): Sequential(\n",
       "    (0): Linear(in_features=2, out_features=30, bias=True)\n",
       "    (1): ReLU()\n",
       "    (2): Linear(in_features=30, out_features=30, bias=True)\n",
       "    (3): ReLU()\n",
       "  )\n",
       "  (output_layer): Linear(in_features=38, out_features=1, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 30
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:45:33.690630Z",
     "start_time": "2025-02-23T02:45:33.686608Z"
    }
   },
   "cell_type": "code",
   "source": [
    "for name, param in model.named_parameters():\n",
    "      print(name, param.shape)"
   ],
   "id": "62b0dafc023c954a",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "deep.0.weight torch.Size([30, 2])\n",
      "deep.0.bias torch.Size([30])\n",
      "deep.2.weight torch.Size([30, 30])\n",
      "deep.2.bias torch.Size([30])\n",
      "output_layer.weight torch.Size([1, 38])\n",
      "output_layer.bias torch.Size([1])\n"
     ]
    }
   ],
   "execution_count": 31
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:46:41.724472Z",
     "start_time": "2025-02-23T02:46:41.720283Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for (datas_deep, datas_wide), labels in dataloader:\n",
    "        datas_deep = datas_deep.to(device)\n",
    "        datas_wide = datas_wide.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas_deep, datas_wide)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ],
   "id": "da85cdea63cfe31d",
   "outputs": [],
   "execution_count": 32
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:47:48.485483Z",
     "start_time": "2025-02-23T02:46:58.589667Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for (datas_deep, datas_wide), labels in train_loader:\n",
    "                datas_deep = datas_deep.to(device)\n",
    "                datas_wide = datas_wide.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas_deep, datas_wide)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 10\n",
    "\n",
    "\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.0)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "id": "f1018f76a2e4ff95",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/14520 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "2c27ce87ada84a8ca59257b1bb406127"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 33
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:48:29.339741Z",
     "start_time": "2025-02-23T02:48:29.096662Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=500)  #横坐标是 steps"
   ],
   "id": "88f2ededdddb3fcd",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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q5JQiYiISH9clnA6duxYdSIiIiKyx7VOiYiIyK0YfBAREZFbMfggIiIit2LwQURERG7F4IOIiIjcyuPl1QuzrnNXlhrxZamHLwvfyGN7U0laV2O72W690Gvb2W622xtYj9uOrFfrdcFHamqqOpfF5YiIiMi3yHG8UqVKJW5j0BwJUdxISrGfOnUKUVFRMBgMTn1s64q5x48fd/qKud6M7Wa79UKvbWe72W5vIOGEBB516tRBQECAb/V8yA7Xq1fPpc8hb5Y3vWHuwnbri17bree2s936Eu2F7S6tx8OKCadERETkVgw+iIiIyK10FXyEhobilVdeUed6wnaz3Xqh17az3Wy3r/G6hFMiIiLyb7rq+SAiIiLPY/BBREREbsXgg4iIiNyKwQcRERG5lW6Cj+nTp6Nhw4YICwtD165dsWnTJviSyZMno0uXLqrya40aNTBkyBAkJiYW2CYrKwtjxoxBtWrVEBkZiTvvvBNnz54tsM2xY8cwaNAgGI1G9TjPPvss8vLyCmyzYsUKdOzYUWVSN2nSBF9//TW8wVtvvaWq3j711FO6aPPJkydx7733qraFh4ejTZs22LJli+1+yRWfMGECateure7v27cv9u/fX+AxLl68iJEjR6pCRJUrV8ZDDz2EtLS0Atv88ccfuO6669TfhlRNnDJlCjzFZDLh5ZdfRlxcnGpT48aN8dprrxVYK8If2r1q1SrceuutqhKkfKbnzZtX4H53tvHHH39E8+bN1TbyGVu4cCE81XZZs+S5555T+xEREaG2uf/++1XVa19ve2nvub3HHntMbTN16lSfb3exNB2YPXu2FhISon355Zfa7t27tYcfflirXLmydvbsWc1XDBgwQPvqq6+0P//8U9uxY4d28803a/Xr19fS0tJs2zz22GNabGystmzZMm3Lli3atddeq3Xv3t12f15enta6dWutb9++2vbt27WFCxdqMTEx2vjx423bHDp0SDMajdozzzyj7dmzR5s2bZoWGBioLV68WPOkTZs2aQ0bNtTatm2rPfnkk37f5osXL2oNGjTQRo8erW3cuFHt45IlS7QDBw7Ytnnrrbe0SpUqafPmzdN27typ3XbbbVpcXJyWmZlp2+amm27S2rVrp23YsEFbvXq11qRJE2348OG2+5OTk7WaNWtqI0eOVJ+tWbNmaeHh4dqnn36qecIbb7yh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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 34
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-23T02:49:45.377268Z",
     "start_time": "2025-02-23T02:49:44.844837Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "id": "a628d24e2f748ca5",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.5389\n"
     ]
    }
   ],
   "execution_count": 35
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
